use crate::errors::{AuthError, MyError, UserNameDupError, UserNotExistsError};
use crate::img_code;
use crate::models::{
    GenCodeResult, LoginResult, LoginWithPwd, Sex, SqlSex, Sqli32, Sqlu8, User, UserInit,
};
use crate::notes_redis_helper;
use crate::notes_redis_helper::Auth;
use crate::utils::{gen_random_code, gen_random_num, to_md5, RandomCode};
use actix_web::{HttpResponse, ResponseError};
use deadpool_postgres::Client;
use lazy_static::lazy_static;
use log::debug;
use redis::Connection;
use std::{collections::HashMap, iter::Iterator, result::Result};

const REDIS_PREFIX_AUTH: &str = "auth";
const REDIS_PREFIX_IMG_CODE: &str = "img_code";

lazy_static! {
    static ref REDIRECT_DICT: HashMap<String, String> =
        HashMap::from([("123456".to_string(), "/app/index.html".to_string())]);
}

fn is_empty<T>(it: &mut dyn Iterator<Item = T>) -> bool {
    it.next().is_none()
}
async fn user_exists(user_name: &str, client: &Client) -> Result<bool, MyError> {
    Ok(!is_empty(
        &mut client
            .query(
                "select 1 from users where user_name=$1",
                &[&user_name.to_owned()],
            )
            .await
            .map_err(MyError::from)?
            .iter(),
    ))
}
async fn assert_user_not_exists(user_name: &str, client: &Client) -> Result<(), MyError> {
    if user_exists(user_name, client).await? {
        return Err(MyError::UserNameDup(UserNameDupError {
            user_name: user_name.to_owned(),
        }));
    }
    Ok(())
}

fn assert_client_id(client_id: &str) -> Result<(), MyError> {
    if REDIRECT_DICT.get(client_id).is_none() {
        return Err(MyError::IncorrectClientId);
    }
    Ok(())
}

async fn assert_user_exists(user_name: &str, client: &Client) -> Result<(), MyError> {
    if !user_exists(user_name, client).await? {
        return Err(MyError::UserNotExists(UserNotExistsError {
            user_name: user_name.to_owned(),
        }));
    }
    Ok(())
}

async fn get_user(user_name: &str, client: &Client) -> Result<User, MyError> {
    let row = client
        .query_one(
            "select user_name from users where user_name=$1",
            &[&user_name],
        )
        .await
        .map_err(MyError::from)?;
    Ok(User {
        user_name: row.get("user_name"),
    })
}
async fn create_user(user: &UserInit, client: &Client) -> Result<(), MyError> {
    let _ = client
        .execute(
            "insert into users (user_name, pwd) values($1, $2)",
            &[&user.user_name, &to_md5(&user.pwd)],
        )
        .await?;
    Ok(())
}

async fn update_user(user: &User, client: &Client) -> Result<(), MyError> {
    /*
    let _ = client
        .execute("update users set sex=$1, alias=$2, birthday_year=$3, birthday_month=$4, birthday_day=$5",
            &[&i32::from(user.sex.clone()), &user.alias.clone().unwrap_or("".to_owned()), &Option::<i32>::from(Sqli32(user.birthday_year)), &Option::<i32>::from(Sqli32(user.birthday_month)), &Option::<i32>::from(Sqli32(user.birthday_day))])
        .await?;
    */
    Ok(())
}

async fn user_auth_exists(user_name: &str, pwd: &str, client: &Client) -> Result<bool, MyError> {
    if !is_empty(
        &mut client
            .query(
                "select 1 from users where user_name=$1 and pwd=$2",
                &[&user_name.to_owned(), &pwd.to_owned()],
            )
            .await
            .map_err(MyError::from)?
            .iter(),
    ) {
        Ok(true)
    } else {
        Err(MyError::AuthError(AuthError {
            message: "用户名/密码错误".to_string(),
        }))
    }
}
async fn assert_user_auth(user_name: &str, pwd: &str, client: &Client) -> Result<(), MyError> {
    if user_auth_exists(user_name, pwd, client).await? {
        Ok(())
    } else {
        Err("用户名/密码验证失败".to_string().into())
    }
}

async fn assert_code_source(code_source: &str, redis_conn: &mut Connection) -> Result<(), MyError> {
    if notes_redis_helper::exists_code_source(redis_conn, code_source)? {
        Ok(())
    } else {
        Err(MyError::Msg("无效code_source".to_string()))
    }
}
fn gen_access_code(user_id: &str) -> Result<String, MyError> {
    Ok(to_md5(&format!("{}-{}", user_id, gen_random_num()?)))
}

// pub_funcs
pub async fn init_user(
    user: &UserInit,
    client: &Client,

    redis_conn: &mut Connection,
) -> Result<User, MyError> {
    debug!("init_user");
    //println!("init_user");
    assert_code_source(&user.code_source, redis_conn).await?;
    assert_user_not_exists(&user.user_name, client).await?;
    create_user(user, client).await?;
    get_user(&user.user_name, client).await
}

pub async fn update_user_info(user: &User, client: &Client) -> Result<User, MyError> {
    assert_user_exists(&user.user_name, client).await?;

    update_user(user, client).await?;
    get_user(&user.user_name, client).await
}

pub async fn login(
    login: &LoginWithPwd,
    client: &Client,
    redis_conn: &mut Connection,
) -> Result<LoginResult, MyError> {
    debug!("client_id:{}", &login.client_id);
    assert_client_id(&login.client_id)?;
    assert_code_source(&login.code_source, redis_conn).await?;
    assert_user_exists(&login.user_name, client).await?;
    assert_user_auth(&login.user_name, &to_md5(&login.pwd), client).await?;

    let access_code = &gen_access_code(&login.user_name)?;

    Auth::from(redis_conn).set_access_code(access_code, &login.user_name)?;

    Ok(LoginResult {
        access_code: access_code.to_owned(),
        redirect_path: REDIRECT_DICT.get(&login.client_id).unwrap().to_owned(),
    })
}

pub fn gen_code_source(redis_conn: &mut Connection) -> Result<String, MyError> {
    let random_num = gen_random_num()?;

    notes_redis_helper::set_code_source(redis_conn, &random_num, "")?;

    Ok(random_num)
}

pub fn gen_img_code(cookie: &str, redis_conn: &mut Connection) -> Result<GenCodeResult, MyError> {
    let text_code: String = gen_random_code()?.into();
    let img = img_code::gen_code_img(&text_code)?;

    notes_redis_helper::set_data_str(redis_conn, REDIS_PREFIX_IMG_CODE, cookie, &text_code)?;

    Ok(GenCodeResult {
        code: cookie.to_owned(),
        img,
    })
}
